/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.viewer;

import java.awt.BorderLayout;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.*;
import java.awt.image.renderable.ParameterBlock;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import org.eclipse.imagen.JAI;
import org.eclipse.imagen.ParameterBlockJAI;
import org.eclipse.imagen.PlanarImage;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.RenderedOp;

// import com.digitalglobe.util.Log4jUtil;

// import org.apache.log4j.Logger;

/**
 * Tabbed {@link RenderedImage} information display
 *
 * @author Andrea Aime
 * @author Daniele Romagnoli, GeoSolutions SAS
 * @author Simone Giannecchini, GeoSolutions SAS
 */
public class RenderedImageInfoPanel extends JPanel {
    // Data buffer type to name map
    private static final Map<Integer, String> TYPE_MAP = new HashMap<Integer, String>();
    // Rendering key to name map
    private static final Map<Object, String> KEY_MAP = new HashMap<Object, String>();

    // private static final Logger logger = Logger.getLogger(RenderedImageInfoPanel.class);
    private static final Logger logger = Logger.getLogger(RenderedImageInfoPanel.class.toString());

    static {
        TYPE_MAP.put(DataBuffer.TYPE_BYTE, "Byte");
        TYPE_MAP.put(DataBuffer.TYPE_DOUBLE, "Double");
        TYPE_MAP.put(DataBuffer.TYPE_FLOAT, "Float");
        TYPE_MAP.put(DataBuffer.TYPE_INT, "Int");
        TYPE_MAP.put(DataBuffer.TYPE_SHORT, "Short");
        TYPE_MAP.put(DataBuffer.TYPE_UNDEFINED, "Undefined");
        TYPE_MAP.put(DataBuffer.TYPE_USHORT, "Short");

        try {
            Field[] fields = JAI.class.getFields();
            for (Field field : fields) {
                final int modifiers = field.getModifiers();
                // funny enough most KEY in JAI are not final...
                if (Modifier.isStatic(modifiers)
                        && Modifier.isPublic(modifiers)
                        && field.getName().startsWith("KEY_")) {
                    KEY_MAP.put(field.get(null), field.getName());
                }
            }
        } catch (Exception e) {
            // Log4jUtil.warn(logger, e.getMessage(), e);

        }
    }

    JTabbedPane tabs;
    JEditorPane generalPanel;
    JEditorPane propertiesPanel;
    private JEditorPane operationPanel;
    ImageViewer viewer;
    private ImageViewer roiViewer;
    private boolean showHistogram = true;
    private DisplayHistogram histogramPanel = new DisplayHistogram("");
    private boolean showRoi;

    public RenderedImageInfoPanel() {
        this(false, false);
    }

    public RenderedImageInfoPanel(final boolean showHistogram) {
        this(showHistogram, false);
    }

    public RenderedImageInfoPanel(final boolean showHistogram, final boolean showRoi) {
        setLayout(new BorderLayout());
        tabs = new JTabbedPane();
        add(tabs, BorderLayout.CENTER);
        this.showHistogram = showHistogram;
        this.showRoi = showRoi;
        generalPanel = buildHtmlPane();
        propertiesPanel = buildHtmlPane();
        operationPanel = buildHtmlPane();

        if (showRoi) {
            roiViewer = new ImageViewer();
            viewer = new ImageViewer(roiViewer);
            roiViewer.setRelatedViewer(viewer);
        } else {
            viewer = new ImageViewer();
        }
        tabs.addTab("General information", scroll(generalPanel));
        tabs.addTab("Viewer", viewer);
        tabs.addTab("Properties", scroll(propertiesPanel));
        tabs.addTab("Operation", scroll(operationPanel));
        if (showHistogram) {
            tabs.addTab("Histogram", scroll(histogramPanel));
        }
        if (showRoi) {
            tabs.addTab("ROIViewer", roiViewer);
        }
    }

    private JScrollPane scroll(JComponent pane) {

        return new JScrollPane(pane);
    }

    private JEditorPane buildHtmlPane() {
        JEditorPane pane = new ScrollTopEditorPane("text/html", "");
        pane.setEditable(false);

        return pane;
    }

    public void setImage(RenderedImage image) {
        buildGeneralInfoPane(image);
        buildPropertiesPane(image);
        if (image instanceof RenderedOp) {
            operationPanel.setEnabled(true);
            buildOpPane((RenderedOp) image);
        } else {
            operationPanel.setText("This image is not an operation");
        }
        if (showHistogram) {
            histogramPanel.setImage(PlanarImage.wrapRenderedImage(image));
        }
        try {
            viewer.setImage(image);
        } catch (Exception e) {
            viewer.setImage(null);
            viewer.setStatusMessage("Error:" + e.getMessage());
            e.printStackTrace();
        }
        if (showRoi) {
            try {
                if (image instanceof RenderedOp) {
                    final Object object = image.getProperty("ROI");
                    if (object instanceof ROI) {
                        ROI roi = (ROI) object;
                        roiViewer.setImage(roi.getAsImage());
                    } else {
                        roiViewer.setImage(null);
                    }
                } else {
                    roiViewer.setImage(null);
                }
            } catch (Exception e) {
                e.printStackTrace();
                roiViewer.setImage(null);
                roiViewer.setStatusMessage("Error:" + e.getMessage());
            }
        }
    }

    void buildOpPane(RenderedOp image) {
        final HTMLBuilder hb = new HTMLBuilder();
        hb.title("Operation");

        String renderingName = "null";
        try {
            image.getWidth();
            renderingName = image.getCurrentRendering().getClass().getName();
        } catch (Exception ignored) {
            // can happen if the op has trouble setting up its rendering
            //            if (logger.isTraceEnabled())
            //            {
            //                Log4jUtil.trace(logger, ignored.getMessage(), ignored);
            //            }

            if (logger.isLoggable(Level.WARNING)) {
                logger.log(Level.WARNING, ignored.getMessage());
            }
        }
        hb.dataLine("Name", image.getOperationName() + " (" + renderingName + ')');

        hb.title("Parameters");

        final ParameterBlock block = image.getParameterBlock();
        Vector<Object> paramValues = block.getParameters();
        for (int i = 0; i < paramValues.size(); i++) {
            String name = "Parameter " + (i + 1);
            if (block instanceof ParameterBlockJAI) {
                name = ((ParameterBlockJAI) block).getParameterListDescriptor().getParamNames()[i];
            }
            hb.dataLine(name, paramValues.get(i));
        }

        hb.title("Hints");

        RenderingHints hints = image.getRenderingHints();
        List keys = new ArrayList(hints.keySet());
        for (Object key : keys) {
            String label = KEY_MAP.get(key);
            if (label == null) {
                label = key.toString();
            }
            hb.dataLine(label, hints.get(key));
        }
        operationPanel.setText(hb.getHtml());
    }

    void buildPropertiesPane(RenderedImage source) {
        HTMLBuilder hb = new HTMLBuilder();
        hb.title("Properties");

        String[] properties = source.getPropertyNames();
        if (properties == null) {
            propertiesPanel.setText("No properties found in this image");
            return;
        }
        Arrays.sort(properties);
        for (String propName : properties) {
            hb.dataLine(propName, source.getProperty(propName));
        }

        propertiesPanel.setText(hb.getHtml());
        operationPanel.setCaretPosition(0);
    }

    void buildGeneralInfoPane(RenderedImage image) {
        HTMLBuilder hb = new HTMLBuilder();
        hb.title("Abstract");
        hb.dataLine("Name", getImageName(image));
        hb.dataLine("Image class", image.getClass());
        hb.dataLine("Image origin", image.getMinX() + " , " + image.getMinY());
        hb.dataLine("Image size", image.getWidth() + " x " + image.getHeight());
        hb.dataLine("[Debug_IDHash]:", Integer.toHexString(System.identityHashCode(image)));

        hb.title("Tiles organisation");

        int tw = image.getTileWidth();
        int th = image.getTileHeight();
        int ytc = image.getNumYTiles();
        int xtc = image.getNumXTiles();
        hb.dataLine("Tile size", tw + " x " + th);
        hb.dataLine(
                "Tile grid",
                xtc + " x " + ytc + //
                        " (" + (tw * xtc) + " x " + (th * ytc) + ')');
        hb.dataLine("Tile offsets x,y", image.getTileGridXOffset() + ", " + image.getTileGridYOffset());
        hb.dataLine("Min tile x,y", image.getMinTileX() + ", " + image.getMinTileY());

        hb.title("Sample model");

        SampleModel sm = image.getSampleModel();
        hb.dataLine("Sample model", sm.getClass());
        hb.dataLine("Size", sm.getWidth() + " x " + sm.getHeight());
        hb.dataLine("Bands", sm.getNumBands());
        hb.dataLine("Bands datatype", TYPE_MAP.get(sm.getDataType()));
        if (sm instanceof ComponentSampleModel) {
            ComponentSampleModel csm = (ComponentSampleModel) sm;
            StringBuilder sb = new StringBuilder();
            int[] bOffsets = csm.getBandOffsets();
            for (int bOffset : bOffsets) {
                sb.append(bOffset + ",");
            }
            sb.setLength(sb.length() - 1);
            hb.dataLine("BandOffsets", sb.toString());
            sb.setLength(0);
            int[] bIndices = csm.getBankIndices();
            for (int bIndex : bIndices) {
                sb.append(bIndex + ",");
            }
            sb.setLength(sb.length() - 1);
            hb.dataLine("BandIndices", sb.toString());
            hb.dataLine("PixelStride", csm.getPixelStride());
            hb.dataLine("ScanlineStride", csm.getScanlineStride());
            sb.setLength(0);
            int[] sampleSizes = csm.getSampleSize();
            for (int sampleSize : sampleSizes) {
                sb.append(sampleSize + ",");
            }
            sb.setLength(sb.length() - 1);
            hb.dataLine("SampleSize", sb.toString());
        }

        hb.title("Color model");
        final ColorModel colorModel = image.getColorModel();
        if (colorModel != null) {
            hb.dataLine("Color model", colorModel.getClass());
            switch (colorModel.getTransparency()) {
                case Transparency.OPAQUE:
                    hb.dataLine("Transparency", "Opaque");
                    break;
                case Transparency.TRANSLUCENT:
                    hb.dataLine("Transparency", "Translucent");
                    break;
                case Transparency.BITMASK:
                    hb.dataLine("Transparency", "Bitmask");
                    break;
            }
            if (colorModel instanceof IndexColorModel) {
                final IndexColorModel icm = (IndexColorModel) colorModel;
                // transparent pixel
                hb.dataLine("Transparent Pixel", Integer.toString(icm.getTransparentPixel()));

                // lut
                int colorMapSize = icm.getMapSize();
                hb.dataLine("ColorMap Size", Integer.toString(colorMapSize));

                final int numbands = icm.hasAlpha() ? 4 : 3;
                final byte[][] cmap = new byte[numbands][icm.getMapSize()];
                icm.getReds(cmap[0]);
                icm.getGreens(cmap[1]);
                icm.getBlues(cmap[2]);
                if (numbands == 4) {
                    icm.getAlphas(cmap[3]);
                }

                final StringBuilder b = new StringBuilder();

                boolean truncated = false;
                if (colorMapSize > 255) {
                    // Truncate it otherwise it will take ages to write the content
                    // Think about extended color palette with 65536 colors
                    colorMapSize = 255;
                    truncated = true;
                }
                for (int i = 0; i < colorMapSize; i++) {
                    b.append('(')
                            .append(i)
                            .append(")-")
                            .append('[')
                            .append(cmap[0][i])
                            .append(',')
                            .append(cmap[1][i])
                            .append(',')
                            .append(cmap[2][i]);
                    if (numbands == 4) {
                        b.append(',').append(cmap[3][i]);
                    }
                    b.append(']').append('\n');
                }
                String colormapHeader = truncated ? "Truncated ColorMap" : "ColorMap";
                if (truncated) {
                    b.append(".... ColorMap was too long. Truncated to 255 Colors");
                }
                hb.dataLine(colormapHeader, b.toString());
            }
            hb.dataLine("Color space", colorModel.getColorSpace().getClass());
        }

        hb.title("Sources");

        List<RenderedImage> sources = image.getSources();
        final int size;
        if ((sources != null) && !sources.isEmpty()) {
            size = sources.size();
        } else {
            size = 0;
        }
        for (int i = 0; i < size; i++) {
            hb.dataLine("Source " + (i + 1), getImageName(sources.get(i)));
        }

        generalPanel.setText(hb.getHtml());
    }

    private String getImageName(RenderedImage source) {
        if (source instanceof RenderedOp) {
            return "RenderedOp, <i>" + ((RenderedOp) source).getOperationName() + "</i>";
        } else {
            return "Generic <i>" + source.getClass() + "</i>";
        }
    }

    /**
     * Simple variation over JEditorPane making the caret be at top each time the text is set into the pane
     *
     * @author Andrea Aime
     */
    private static class ScrollTopEditorPane extends JEditorPane {

        public ScrollTopEditorPane(String type, String text) {
            super(type, text);
        }

        @Override
        public void setText(String t) {
            super.setText(t);
            setCaretPosition(0);
        }
    }
}
